#!/usr/bin/python3
# -*- coding: utf-8 -*
"""
Description: Config parser.
Class: ConfigParser
"""
import os
import re
import json
import xml
import yaml
import xmltodict

from ..libs.log import LOGGER
from . import Parser


class ConfigParser(Parser):
    """
    class ConfigParser is the parser that parses the configuration to the formmated data.

    Attributes:
        switch: refer to different configuration parsing functions.
    """

    def __init__(self, param_config, regex_config):
        """
        Class instance initialization.

        Args:
            regex_config (dict): configuration which keeps compiled patterns,
                                original patterns and other related information.
            param_config (dict): configuration which keeps corresponding log information.
        """
        self.switch = {"xml": self.parse_xml, "yaml_or_json": self.parse_json_or_yaml,
                       "ini": self.parse_ini_config, "normal": self.parse_config}
        super().__init__(param_config, regex_config)

    def parse_content(self, key, content):
        """
        Parse the configuration to formmated data.

        Args:
            key (str): the main key of the content.
            content (str): raw data.

        Returns:
            dict: parsed content.
            bool: parse succeed or fail.
        """
        config_flag = self.param_config.get('config_flag')
        content = content if isinstance(content, list) else [content]

        # when the config type is not specified, try xml/yaml/json first.
        if config_flag is None:
            config_flag = self.find_config_type(content)
            self.param_config['config_flag'] = config_flag

        # use matched parse method.
        if config_flag in ('xml', 'yaml_or_json'):
            try:
                res = self.switch[config_flag](content)
                return res, True
            except (xml.parsers.expat.ExpatError,
                    yaml.scanner.ScannerError,
                    yaml.parser.ParserError):
                return content, False

        # remove the comment line and blank line.
        content = self.get_active_lines(content)

        if len(content) == 0:
            LOGGER.warning("'%s' has no content exclude comment line.", key)
            return {}, None, True

        matched_grok = self.param_config.get('matched_grok')

        if matched_grok is None:
            head = None
            # judge the config file validation.
            for line in content:
                if not line.startswith('['):
                    head = line
                    break
            if head is None:
                LOGGER.warning(
                    "'%s' is a empty ini configuration file which just has heads, return [].", key)
                return [], False
            matched_grok = self.find_config_pattern(key, head)
            # don't find matched pattern
            if matched_grok is None:
                return content, None, False
            self.param_config['matched_grok'] = matched_grok

        res = self.switch[config_flag](content, matched_grok)

        return res, True

    def find_config_type(self, content):
        """
        find configuration type.

        Args:
            content (list): raw data.

        Returns:
            str: configuration type.
        """
        file_path = self.param_config.get('file_path')
        file_suffix = os.path.splitext(file_path)[1].strip(
            '.') if isinstance(file_path, str) else ""
        if file_suffix == 'xml':
            config_flag = 'xml'
        elif file_suffix in ('json', 'yml'):
            config_flag = 'yaml_or_json'
        else:
            # try to parse the configuration file using yaml, json or xml method.
            config_flag = self.complex_config_parse(content)

        if config_flag != 'unknown':
            return config_flag
        # remove the comment line and blank line.
        content = self.get_active_lines(content)
        if len(content) == 0:
            return "normal"
        head = content[0]
        # judge whether the configuration file is ini configuration, e.g.
        # [head]
        # key : value
        if head.startswith('[') and head.endswith(']'):
            return 'ini'
        return "normal"

    def find_config_pattern(self, key, head):
        """
        find configuration pattern.

        Args:
            key (str): key of the content.
            head (str): head of the content, just used to find the matched pattern.

        Returns:
            grok object: matched compiled pattern.
        """
        # get matched grok
        matched_grok, _ = self.get_matched_pattern(key, head)

        if matched_grok is None:
            matched_grok, _ = self.find_matched_pattern_by_type(head, "config")

            if matched_grok is None:
                LOGGER.error(
                    "No matched pattern for '%s', return raw content.", key)
                return None
        return matched_grok

    def complex_config_parse(self, content):
        """
        Try xml/json/yaml parse function.

        Args:
            content (list): data need to be parsed.

        Returns:
            str: refer to parse function.
        """
        config_flag = 'unknown'

        try:
            content = self.parse_xml(content)
            config_flag = 'xml'
        except xml.parsers.expat.ExpatError:
            config_flag = 'unknown'

        if config_flag != 'xml':
            try:
                content = self.parse_json_or_yaml(content)
                if isinstance(content, dict):
                    config_flag = 'yaml_or_json'
            except yaml.scanner.ScannerError:
                config_flag = 'unknown'
            except yaml.parser.ParserError:
                config_flag = 'unknown'

        return config_flag

    @staticmethod
    def parse_xml(content):
        """
        Xml parse function.

        Args:
            content (list): data need to be parsed.

        Returns:
            dict: parsed content.
        """
        xml_parse = xmltodict.parse('\n'.join(content))
        content = json.loads(json.dumps(xml_parse))

        return content

    @staticmethod
    def parse_json_or_yaml(content):
        """
        Json/yaml parse function.

        Args:
            content (list): data need to be parsed.

        Returns:
            dict: parsed content.
        """
        content = yaml.safe_load('\n'.join(content))

        return content

    @staticmethod
    def parse_config(content, grok):
        """
        Parse normal configuration content.

        Args:
            content (list): active lines.
            grok (grok object): related grok pattern that has been compiled.

        Returns:
            dict: formatted data or original data.
        """
        log_formated = {}

        for line in content:
            tmp = grok.match(line)
            if tmp is None or tmp == {}:
                continue
            tmp_key = tmp['key'].strip(' -"\t')
            value = tmp['value'].strip(' [,">-')
            if tmp_key in log_formated.keys():
                stored_value = log_formated[tmp_key]
                if isinstance(stored_value, list):
                    log_formated[tmp_key].append(value)
                else:
                    log_formated[tmp_key] = [stored_value, value]
            else:
                log_formated[tmp_key] = value

        return log_formated

    @staticmethod
    def parse_ini_config(content, grok):
        """
        Parse ini configuration content.

        Args:
            content (list): active lines.
            grok (grok object): related grok pattern that has been compiled.

        Returns:
            dict: formatted data.
        """
        # head pattern that can find the head "[]"
        head_pattern = re.compile(
            r'\['
            r'(?P<header>[^]]+)'
            r'\]'
        )
        log_formated = {}
        cursect = {}
        subkey = None

        for line in content:
            # multiple line
            if subkey and len(cursect) != 0 and head_pattern.match(line) is None:
                value = line.strip()
                if value:
                    cursect[subkey] += value
            else:
                # is it a section header?
                cur_match = head_pattern.match(line)
                if cur_match:
                    sectname = cur_match.group('header')
                    if sectname in log_formated:
                        cursect = log_formated[sectname]
                    else:
                        cursect = dict()
                        log_formated[sectname] = cursect
                    # so sections can't start with a continuation line
                    subkey = None
                else:
                    tmp = grok.match(line)
                    if tmp is None or tmp == {}:
                        continue
                    subkey = tmp['key'].strip(' -"')
                    value = tmp['value'].strip(' [],"')
                    cursect[subkey] = value

        return log_formated


def get_class():
    """
    get ConfigParser class.

    Args:

    Returns:
        class
    """
    return ConfigParser
